Cognito でユーザープールベースのマルチテナンシーを選択した際に、API Gateway で複数ユーザープールを許可する Cognito ユーザープールオーソライザーを SAM で作成してみた
いわさです。
Cognito を認証サービスとして使用するアプリケーションでマルチテナントを実現しようとする場合、次のようにいくつかの選択肢があります。
マルチテナントの認証・認可要件によってどれを選択するべきか変わってくるのですが、ユーザープール単位で共通設定されるオプションがテナントごとに差異が出る場合はユーザープールベースのマルチテナンシーを選択する形になります。
例えば、テナント A と テナント B で MFA の有無やパスワードポリシーが異なる場合などです。
ただし上記ドキュメントのユーザープールベースのシナリオのうち、以下について気になりました。
アプリケーションに、各テナントがその使用のためにアプリケーションインフラストラクチャの完全なインスタンスを取得する、サイロ型のマルチテナントアプリケーションがある。
ユーザープールごとにサイロ型で分離されたインフラ環境であればマルチテナンシーを実現出来るのはまぁそうかという感じです。
ただし、今回は API Gateway をベースにアーキテクチャーを検討していました。
その場合でもテナントごとに用意する必要があるのでしょうか。そういえば、ひとつの統合で設定出来るオーソライザーは 1 つまでです。
カスタムオーソライザーであれば根性入れればどうにかなりそうですが、Cognito ユーザープールオーソライザーについてはちょっと実現性について自信がないです。
そこで、今回はユーザープールベースのマルチテナンシー戦略を採用した場合に API Gateway の Cognito ユーザープールオーソライザーを使えるのかどうかを検証してみたいと思います。
API Gateway は REST API です。
適当なサーバーレスアプリケーション を作成
まずは検証対象の適当なサーバーレスアプリを作成します。
sam init
して適当なテンプレートの関数をいくつか複製しておきます。
次に Application Composer を使って Lambda に色々とコンポーネントを接続していきます。
最後にテンプレートをまた少し手動で調整します。
以下のような感じにしました。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: '---' Globals: Function: Timeout: 10 MemorySize: 256 Architectures: - x86_64 Runtime: dotnet6 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./src/HelloWorld/ Handler: HelloWorld::HelloWorld.Function::FunctionHandler Events: HogeApiGET: Type: Api Properties: Path: / Method: GET RestApiId: !Ref HogeApi HogeFunction1: Type: AWS::Serverless::Function Properties: CodeUri: ./src/HogeFunc1/ Handler: HogeFunc1::HogeFunc1.Function::FunctionHandler Events: HogeApiGEThoge1: Type: Api Properties: Path: /hoge1 Method: GET RestApiId: !Ref HogeApi Auth: Authorizer: Auth1 HogeFunction2: Type: AWS::Serverless::Function Properties: CodeUri: ./src/HogeFunc2/ Handler: HogeFunc2::HogeFunc2.Function::FunctionHandler Events: HogeApiGEThoge2: Type: Api Properties: Path: /hoge2 Method: GET RestApiId: !Ref HogeApi Auth: Authorizer: Auth2 HogeApi: Type: AWS::Serverless::Api Properties: Name: hoge0313Api StageName: Prod EndpointConfiguration: REGIONAL TracingEnabled: true Auth: Authorizers: Auth1: UserPoolArn: !GetAtt UserPool1.Arn Auth2: UserPoolArn: !GetAtt UserPool2.Arn UserPool1: Type: AWS::Cognito::UserPool Properties: AdminCreateUserConfig: AllowAdminCreateUserOnly: true AliasAttributes: - email - preferred_username UserPoolName: !Sub ${AWS::StackName}-UserPool1 UserPool2: Type: AWS::Cognito::UserPool Properties: AdminCreateUserConfig: AllowAdminCreateUserOnly: false AliasAttributes: - email - preferred_username UserPoolName: !Sub ${AWS::StackName}-UserPool2 UserPoolClient1: Type: AWS::Cognito::UserPoolClient Properties: UserPoolId: !Ref UserPool1 ExplicitAuthFlows: - ALLOW_USER_PASSWORD_AUTH - ALLOW_REFRESH_TOKEN_AUTH UserPoolClient2: Type: AWS::Cognito::UserPoolClient Properties: UserPoolId: !Ref UserPool2 ExplicitAuthFlows: - ALLOW_USER_PASSWORD_AUTH - ALLOW_REFRESH_TOKEN_AUTH
こちらをデプロイすると、ルート含めて 3 つのリソースが用意されている API が作成されます。
ルートはオーソライザーは設定されておらず、hoge1
にはオーソライザー A が、hoge2
にはオーソライザー B が設定されています。
匿名アクセスしてみると次のようになります。
% curl https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod {"message":"hello world","location":"3.115.15.210"} % curl https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod/hoge1 {"message":"Unauthorized"} % curl https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod/hoge2 {"message":"Unauthorized"}
hoge1 と hoge2 は Cognito ユーザープールのトークンが必要なので、期待どおりの動作ですね。
Application Composer はスケルトンコードの生成は弱いのと、細かいところは最終的にはテンプレート修正で調整する必要があるので、今回のような使い方を私は最近しています。
1 つのオーソライザーに複数の Cognito ユーザープールを追加
今の状態だと、hoge1 はユーザープール 1 でアクセス可能、hoge2 はユーザープール 2 でアクセス可能という形になっています。
これはプール型では非常に良くないです。テナントの数だけリソースが増えてしまう。
そこで hoge1 にはどちらにテナントでもアクセス出来るようにしてみたいと思います。
マネジメントコンソールからは 1 つのオーソライザーに指定出来るユーザープールは 1 つなのですが、試してみたところどうやら API や IaC で指定する場合はオーソライザーのユーザープール ARN は複数の指定することも出来るようです。
次のように単独のオーソライザーに複数テナントを想定したユーザープールを指定してみました。
: HogeApi: Type: AWS::Serverless::Api Properties: Name: hoge0313Api StageName: Prod EndpointConfiguration: REGIONAL TracingEnabled: true Auth: Authorizers: Auth1: UserPoolArn: - !GetAtt UserPool1.Arn - !GetAtt UserPool2.Arn Auth2: UserPoolArn: !GetAtt UserPool2.Arn UserPool1: Type: AWS::Cognito::UserPool :
デプロイ後にマネジメントコンソールを確認してみると...
複数設定されていますね。これはいけそうだ。
あとはトークンを取得してそれぞれの API へアクセスしてみましょう。
今回は以下の記事を参考に AWS CLI 経由でトークンを取得してみました。
% cat hogeuser1.json { "AuthFlow": "USER_PASSWORD_AUTH", "ClientId": "4ekqh42ac992nj5a08iaqtqjqn", "AuthParameters": { "USERNAME": "hogeuser1", "PASSWORD": "hogehoge" } } % aws cognito-idp initiate-auth --cli-input-json file://hogeuser1.json { "ChallengeParameters": {}, "AuthenticationResult": { "AccessToken": "eyJraWQiO...lKNpu0_Q", "ExpiresIn": 3600, "TokenType": "Bearer", "RefreshToken": "eyJjdHkiOiJ...4hLYlhiUKbAg", "IdToken": "eyJraWQiOi...wUf5b_dAQ" } }
テナント 1 のユーザー
% curl -H "Authorization: eyJr..._dAQ" "https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod/hoge1" {"message":"HogeFunc1"} % curl -H "Authorization: eyJr..._dAQ" "https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod/hoge2" {"message":"Unauthorized"}
テナント 2 のユーザー
% curl -H "Authorization: eyJr...SJdg" "https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod/hoge1" {"message":"HogeFunc1"} % curl -H "Authorization: eyJr...SJdg" "https://vj3la9qlde.execute-api.ap-northeast-1.amazonaws.com/Prod/hoge2" {"message":"HogeFunc2"}
複数のユーザープールを紐付けたオーソライザーでは対象のユーザープールからのアクセスを許可することが出来ました。
オーソライザーに関連付けるユーザープールの上限
API あたりのオーソライザーの最大は 10 (上限緩和可能) という情報が公開されています。
ただし、1 オーソライザーに関連付け出来るユーザープールの上限は不明です。
参考までに、私が試したところ 51 個までは関連付け出来ることを確認しました。
さいごに
本日は Cognito でユーザープールベースのマルチテナンシーを選択した際に、オーソライザーに複数のユーザープールを紐付けて API Gateway で使ってみました。
結論としては単一の Cognito ユーザープールオーソライザーに複数のユーザープールを設定することが出来ました。
今回はシンプルに ID トークンで通してみましたが、細かいアクセス制限をどのようにするべきかも今後考えていきたいと思います。